package top.linyuxb.lycloud.gateway.starter.utils.interceptor;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.http.HttpStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import top.linyuxb.lycloud.common.properties.LyGatewayProperties;
import top.linyuxb.lycloud.gateway.starter.annotation.LyGatewayAccess;
import top.linyuxb.lycloud.gateway.starter.dto.LyGatewayUserInfoDto;
import top.linyuxb.lycloud.gateway.starter.exception.ResponseCodeEnum;
import top.linyuxb.lycloud.gateway.starter.exception.ServerException;

import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.util.Objects;

/**
 * Created by Intellij IDEA.
 *
 * @Author: linyuxb
 * @Date: 2024/8/16 11:42
 * @Desc:
 */
@Slf4j
public class AadHandlerInterceptorUtil {

    public static HandlerInterceptor buildHandlerInterceptor(LyGatewayProperties.LyGatewayAadProperties aadProperties) {
        return new HandlerInterceptor() {
            private static final Cache<String, Jwk> cache = CacheUtil.newFIFOCache(500, 24 * 60 * 60 * 1000);
            private static final String JWKS_URI = "jwks_uri";
            private static final String TID = "tid";
            // 客户端id
            private static final String AUD = "aud";
            private static final String PREFERRED_USERNAME = "preferred_username";
            private static final String NBF = "nbf";
            private static final String EXP = "exp";

            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) throws Exception {
                String requestURI = request.getRequestURI();
                // 1、检查过滤url
                if (StringUtils.isNotBlank(aadProperties.getFilterUrls())) {
                    String[] filterUrls = aadProperties.getFilterUrls().split(",");
                    for (String filterUrl : filterUrls) {
                        if (requestURI.contains(filterUrl)) {
                            return false;
                        }
                    }
                }
                // 2、检查允许url
                if (StringUtils.isNotBlank(aadProperties.getAllowUrls())) {
                    String[] filterUrls = aadProperties.getAllowUrls().split(",");
                    for (String filterUrl : filterUrls) {
                        if (requestURI.contains(filterUrl)) {
                            return true;
                        }
                    }
                }
                // 3、检查注解标注
                HandlerMethod handlerMethod = (HandlerMethod) obj;
                Method m = handlerMethod.getMethod();
                LyGatewayAccess access = m.getAnnotation(LyGatewayAccess.class);
                if (Objects.nonNull(access) && access.value()) {
                    return true;
                }
                // 4、执行aad登入
                this.buildUserInfo(request);

                return HandlerInterceptor.super.preHandle(request, response, obj);
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            }

            /**
             * 构建用户信息
             *
             * @param request
             */
            private void buildUserInfo(HttpServletRequest request) {
                String jwt = request.getHeader(aadProperties.getJwtKey());
                Cookie[] cookies = request.getCookies();
                if (Strings.isBlank(jwt)) {
                    if (Objects.nonNull(cookies)) {
                        for (Cookie cookie : cookies) {
                            if (cookie.getName().equalsIgnoreCase(aadProperties.getJwtKey())) {
                                jwt = cookie.getValue();
                                break;
                            }
                        }
                    }
                }

                // jwt非空时解析jwt
                String email = this.authentication(jwt);
                if (StringUtils.isBlank(email)) {
                    throw new ServerException("jwt参数解析", ResponseCodeEnum.FAILED.getCode());
                }
                // 开始效验管理员权限
                log.info("[user login] [email:{}]", email);

                request.setAttribute("userInfo", LyGatewayUserInfoDto.builder().userEmail(email).build());
            }

            /**
             * 认证用户邮箱
             *
             * @param tokenValue
             * @return
             */
            private String authentication(String tokenValue) {
                //存在令牌
                if (StringUtils.isNotBlank(tokenValue)) {
                    boolean status = this.doAuthentication(tokenValue, true);
                    if (!status) {
                        throw new ServerException("无权限", HttpStatus.UNAUTHORIZED.value());
                    }
                    //获取用户明细信息的字符串
                    String userDetailString =
                            org.springframework.util.StringUtils.delimitedListToStringArray(tokenValue, ".")[1];
                    String jwtBody = cn.hutool.core.codec.Base64.decodeStr(userDetailString);
                    JSONObject result = JSON.parseObject(jwtBody);
                    if (Objects.isNull(result)) {
                        log.info("[请求User-Token解析失败] - [token:{}]", tokenValue);
                        return null;
                    }
                    // 开始AAD鉴权
                    if (StringUtils.isBlank(result.getString(TID))) {
                        log.info("[签名:{}] - [租户ID不能为空] - [jwtBody:{}]", tokenValue, jwtBody);
                        throw new ServerException("无权限", HttpStatus.UNAUTHORIZED.value());
                    }
                    if (StringUtils.isNotBlank(aadProperties.getTid()) &&
                            !aadProperties.getTid().contains(result.getString(TID))) {
                        log.info("[签名:{}] - [租户ID非法] - [jwtBody:{}] - [stsTid:{}]", tokenValue, jwtBody, aadProperties.getTid());
                        throw new ServerException("无权限", HttpStatus.UNAUTHORIZED.value());
                    }
                    if (StringUtils.isBlank(result.getString(AUD))) {
                        log.info("[签名:{}] - [应用ID不能为空] - [jwtBody:{}]", tokenValue, jwtBody);
                        throw new ServerException("无权限", HttpStatus.UNAUTHORIZED.value());
                    }
                    if (StringUtils.isNotBlank(aadProperties.getAppId()) &&
                            !aadProperties.getAppId().contains(result.getString(AUD))) {
                        log.info("[签名:{}] - [应用ID非法] - [jwtBody:{}] - [stsAppId:{}]", tokenValue, jwtBody, aadProperties.getAppId());
                        throw new ServerException("无权限", HttpStatus.UNAUTHORIZED.value());
                    }
                    if (StringUtils.isBlank(result.getString(PREFERRED_USERNAME))) {
                        log.info("[签名:{}] - [用户邮箱为空] - [jwtBody:{}]", tokenValue, jwtBody);
                        throw new ServerException("无权限", HttpStatus.UNAUTHORIZED.value());
                    }
//                    long current = System.currentTimeMillis() / 1000;
//                    long nbf = result.getLongValue(NBF);
//                    long exp = result.getLongValue(EXP);
//                    if (nbf == 0L || exp == 0L || nbf > current || exp < current) {
//                        log.info("[签名:{}] - [过期验签失败] - [jwtBody:{}]", tokenValue, jwtBody);
//                        throw new ServerException("无权限", HttpStatus.UNAUTHORIZED.value());
//                    }
                    return result.getString(PREFERRED_USERNAME);
                }
                log.info("[请求User-未获取认证邮箱] - [token:{}]", tokenValue);
                return null;
            }

            /**
             * 执行jwt认证
             *
             * @param token
             * @param canReset 是否可重置
             * @return
             */
            private boolean doAuthentication(String token, boolean canReset) {
                DecodedJWT jwt = JWT.decode(token);
                String key = jwt.getClaim(TID).asString() + "_" + jwt.getKeyId();
                Jwk jwk = cache.get(key);
                if (!canReset || Objects.isNull(jwk)) {
                    try {
                        long start = System.currentTimeMillis();
                        log.info("[doAuthentication] - [configuration start:{}]", start);
                        String result = HttpUtil.get(aadProperties.getConfigUrl());
                        log.info("[doAuthentication] -  [configuration end:{}] [configuration:{}]", System.currentTimeMillis() - start, result);
                        JSONObject json = JSON.parseObject(result);
                        String jwksUri = json.getString(JWKS_URI);
                        JwkProvider provider = new UrlJwkProvider(new URL(jwksUri));
                        jwk = provider.get(jwt.getKeyId());
                        log.info("[doAuthentication] -  [jwk end:{}] [jwk:{}]", System.currentTimeMillis() - start, JSON.toJSONString(jwk));
                        cache.put(key, jwk);
                    } catch (MalformedURLException e) {
                        e.printStackTrace();
                        log.info("[printStackTrace] : ", e);
                        return false;
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.info("[Exception] : ", e);
                        return false;
                    }
                }
                if (Objects.isNull(jwk)) {
                    log.info("[doAuthentication] - [jwk is null]");
                    return false;
                }
                try {
                    Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
                    algorithm.verify(jwt);
                    /* 整体验签，包括 过期
                    JWTVerifier verifier = JWT.require(algorithm)
                            .withIssuer(jwt.getIssuer())
                            .build();
                    verifier.verify(jwt);
                     */
                    log.info("JWT Validation completed.");
                    return true;
                } catch (JwkException e) {
                    e.printStackTrace();
                    log.info("[JwkException] : ", e);
                } catch (SignatureVerificationException e) {
                    e.printStackTrace();
                    log.info("[SignatureVerificationException] : ", e);
                } catch (Exception e) {
                    e.printStackTrace();
                    log.info("[Exception] : ", e);
                }
                if (canReset) {
                    // 为防止是缓存参数导致失败，需重新请求覆盖缓存参数
                    return this.doAuthentication(token, false);
                }
                return false;
            }
        };
    }
}
